package com.harth.flutter_ns_ble

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import no.nordicsemi.android.ble.callback.WriteProgressCallback
import no.nordicsemi.android.ble.data.DataSplitter
import no.nordicsemi.android.ble.ktx.suspend
import java.io.File
import java.io.FileNotFoundException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.collections.chunked
import kotlin.collections.plus
import kotlin.collections.toByteArray
import kotlin.math.ceil

@OptIn(ExperimentalStdlibApi::class)
suspend fun PeripheralManager.dfu(address:String, peripheral: PeripheralManager.Peripheral, filePath:String) = withContext(Dispatchers.IO){
    callSink(
        DFUStateEvent(
            address = address,
            state = DFUState.START
        )
    )
    val file = File(filePath)
    if (!file.exists()) {
        throw FileNotFoundException(filePath)
    }
    val binFileBytes = file.readBytes()
    val fileSize = binFileBytes.size
    val totalTime = ceil(fileSize / 1024f).toInt()
    var bytesBuilder = byteArrayOf(1)
    bytesBuilder += ByteBuffer.allocate(4)
        .order(ByteOrder.LITTLE_ENDIAN)
        .putInt(fileSize)
        .array()

    val checksum = binFileBytes.sumOf { it.toInt() and 0xff }

    bytesBuilder += ByteBuffer.allocate(4)
        .order(ByteOrder.LITTLE_ENDIAN)
        .putInt(checksum)
        .array()

    val data = bytesBuilder
    val resp = peripheral.request(CMD.DFU_INIT, data)
    if (resp.data?.first() != Codes.ASK_CODE_SUCCESS) {
        throw BleResponseException("CMD.DFU_INIT response error:${resp.data?.toHexString()}")
    }
    val binArray = binFileBytes.toList().chunked(1024)
    var totalSentSize = 0
    binArray.forEachIndexed({ index, item ->

        var byteArray = ByteBuffer.allocate(2)
            .order(ByteOrder.LITTLE_ENDIAN)
            .putShort((index + 1).toShort())
            .array()
        byteArray += item.toByteArray()
        val askCode = async {
            notifyFlow.filter { it.cmd == CMD.DFU_PACKET }.first()
        }
        val packet = BlePacket.fromCmd(CMD.DFU_PACKET.toInt(), byteArray)
        peripheral.writeCharacteristic(packet.byteArray)
            .split(WriteProgressCallback { device, data, index ->
                totalSentSize += data?.size ?: 0
                callSink(
                    DFUStateEvent(
                        address = address,
                        state = DFUState.PROGRESS,
                        sentSize = totalSentSize.toLong(),
                        fileSize.toLong()
                    )
                )
            })
            .suspend()
        Log.e(
            "PeripheralManager",
            "DFU_PACKET totalSentSize:$totalSentSize,fileSize:$fileSize"
        )
        val resp = askCode.await()
        if (resp.data?.first() != Codes.ASK_CODE_SUCCESS) {
            throw BleResponseException("CMD.DFU_PACKET response error:${resp.data?.toHexString()}")
        }
    })

    delay(1000)
    callSink(
        DFUStateEvent(
            address = address,
            state = DFUState.PROGRESS,
            sentSize = fileSize.toLong(),
            totalSize = fileSize.toLong()
        )
    )
    val checkResp = peripheral.request(CMD.DFU_CHECK, null)
    if (checkResp.data?.first() != Codes.ASK_CODE_SUCCESS) {
        throw BleResponseException("CMD.DFU_CHECK response error:${checkResp.data?.toHexString()}")
    }
    peripheral.writeCharacteristic(
        BlePacket.fromCmd(
            CMD.DFU_END.toInt(),
            null
        ).byteArray
    )
        .suspend()

    callSink(
        DFUStateEvent(
            address = address,
            state = DFUState.COMPLETED
        )
    )
}